
package com.jf.cloud.product.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.jf.cloud.common.cache.constant.CacheNames;
import com.jf.cloud.common.cache.constant.LockCacheNames;
import com.jf.cloud.common.cache.constant.ProductCacheNames;
import com.jf.cloud.common.cache.util.RedisUtil;
import com.jf.cloud.common.constant.Constant;
import com.jf.cloud.common.util.BooleanUtil;
import com.jf.cloud.product.bo.NeedZoneSkuInfoBO;
import com.jf.cloud.product.constant.SkuZoneConstant;
import com.jf.cloud.product.mapper.*;
import com.jf.cloud.product.model.SkuStock;
import com.jf.cloud.product.model.SkuStockZone;
import com.jf.cloud.product.service.SkuStockService;
import com.jf.cloud.product.service.SkuStockZoneService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;

/**
 * 
 *
 * @author zz
 * @date 2022-08-17 09:12:37
 */
@Service
public class SkuStockZoneServiceImpl implements SkuStockZoneService {
    private static final Logger log = LoggerFactory.getLogger(SkuStockZoneServiceImpl.class);


    @Autowired
    private SkuStockZoneMapper skuStockZoneMapper;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private SkuStockMapper skuStockMapper;
    @Autowired
    private SpuExtensionMapper spuExtensionMapper;
    @Autowired
    private SpuMapper spuMapper;
    @Autowired
    @Lazy
    private SkuStockService skuStockService;
    @Autowired
    private SkuStockLockMapper skuStockLockMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void doZone(Long skuId) {
        RLock lock = redissonClient.getLock(LockCacheNames.LOCK_ZONE_PREFIX + skuId);
        try {
            lock.lock();
            // 要进行分片，这里面的数据直接从数据库拿，不要从缓存拿
            SkuStock skuStock = skuStockMapper.getBySkuId(skuId);
            // 有可能已经分过片了，避免因为并发导致重复分片
            if (BooleanUtil.isTrue(skuStock.getHasZone())) {
                return;
            }
            Integer stock = skuStock.getStock();

            // 库存小于200不用分片
            if (stock < SkuZoneConstant.NEED_ZONE_NUM) {
                return;
            }
            // 分片数量
            int zoneNum = getZoneNum(stock);
            // 主分片库存
            int mainStock = getMainStock(stock, zoneNum);

            // 剩余要进行拆分的库存
            stock = stock - mainStock;

            // 更新主分片库存，这里的库存是要扣减掉用来分片的库存就是主分片的库存
            skuStock.setStock(stock);
            // 真实的库存通过log来查询
            skuStock.setActualStock(0);
            skuStock.setLimitZone(zoneNum);
            skuStock.setHasZone(1);
            skuStockMapper.update(skuStock);

            List<SkuStockZone> skuStockZones = getSkuStockZones(skuId, stock, zoneNum, skuStock.getSpuId(), false);

            // 插入各个分片库存
            skuStockZoneMapper.batchSave(skuStockZones);
        } finally {
            lock.unlock();
        }
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void redoZone(Long skuId, int stock, Long spuId) {
        if (stock < SkuZoneConstant.NEED_ZONE_NUM) {
            // 库存小于200不用重新分片
            SkuStockZoneServiceImpl skuStockZoneService = (SkuStockZoneServiceImpl) AopContext.currentProxy();
            skuStockZoneService.undoZone(skuId, stock, spuId);
            return;
        }
        // 分片数量
        int zoneNum = getZoneNum(stock);
        // 主分片库存
        int mainStock = getMainStock(stock, zoneNum);

        // 剩余要进行拆分的库存
        stock = stock - mainStock;

        // 更新分片和库存，主分片库存 = 主分片库存 - 要进行分片的总库存
        skuStockMapper.updateStockAndLimitZone(skuId, stock, zoneNum);

        // 有锁定库存这个字段不能删除，所以直接进行更新分片
        List<SkuStockZone> skuStockZones = getSkuStockZones(skuId, stock, zoneNum, spuId, true);

        skuStockZoneMapper.updateBatch(skuStockZones);

//        // 不进行分片更新了，插入10条记录比更新10条记录快，所以我们删掉这里面的信息，然后重新建立过新分片
//        skuStockZoneMapper.deleteBySkuId(skuId);
//
//        List<SkuStockZone> skuStockZones = getSkuStockZones(skuId, stock, zoneNum, spuId);
//        // 插入各个分片库存
//        skuStockZoneMapper.batchSave(skuStockZones);
    }

    /**
     * 取消库存分片
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void undoZone(Long skuId, int stock, Long spuId) {
        // 1.还原sku库存及其锁定库存及分片状态和实际库存
        Integer lockStock = skuStockZoneMapper.getLockStockBySkuId(skuId);
        log.info("sku-lockStock: " + lockStock);
        Integer mainLockStock = skuStockMapper.getLockStockBySkuId(skuId);
        log.info("sku-mainLockStock: " + mainLockStock);
        // 实际库存
        Integer skuActualStock = lockStock + stock;

        // 2.如果该spu已经没有其他sku分片了，还原spu库存及其锁定库存及分片状态
        if (spuExtensionMapper.getRemainingZoneSkuCount(spuId, skuId) == 0) {
            // 总库存(可售卖)
            Integer mainStockBySpuId = skuStockMapper.getMainStockBySpuId(spuId);
            log.info("spu-mainStockBySpuId: " + mainStockBySpuId);
            Integer totalZoneStockBySpuId = skuStockZoneMapper.getTotalZoneStockBySpuId(spuId);
            log.info("spu-totalZoneStockBySpuId: " + totalZoneStockBySpuId);
            int spuTotalStock = mainStockBySpuId + totalZoneStockBySpuId;

            // 锁定库存
            Integer mainLockStockBySpuId = skuStockMapper.getLockStockBySpuId(spuId);
            log.info("spu-mainLockStockBySpuId: " + mainLockStockBySpuId);
            Integer totalZoneLockStockBySpuId = skuStockZoneMapper.getTotalLockStockBySpuId(spuId);
            log.info("spu-totalZoneLockStockBySpuId: " + totalZoneLockStockBySpuId);
            int spuTotalLockStock = mainLockStockBySpuId + totalZoneLockStockBySpuId;

            // 实际库存 = 可售卖库存+锁定库存
            Integer actualStock = spuTotalStock + spuTotalLockStock;
            // 更新商品库存
            spuExtensionMapper.updateSpuToUndoZone(spuId, spuTotalStock, actualStock, spuTotalLockStock);
        }
        skuStockMapper.updateSkuToUndoZone(skuId, stock, skuActualStock, lockStock + mainLockStock);

        // 3.删除次分片
        skuStockZoneMapper.deleteBySkuId(skuId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void zoneSkuIfNecessary(List<Long> spuIds, List<NeedZoneSkuInfoBO> needZoneSkus) {
        // 将商品标记成已经里面有sku被分片的状态
        spuMapper.updateToZone(spuIds);

        // 进行数据分片
        for (NeedZoneSkuInfoBO zoneSku : needZoneSkus) {
            Long skuId = zoneSku.getSkuId();
            doZone(skuId);
        }
    }



    @Override
    @Transactional(rollbackFor = Exception.class)
    public void reZoneByCollection(Collection<Long> skuIds) {
        if (CollectionUtil.isEmpty(skuIds)) {
            return;
        }
        log.info("对sku库存进行重新分片。。。{}", skuIds);
        for (Long skuId : skuIds) {
            RLock lock = redissonClient.getLock(LockCacheNames.LOCK_ZONE_PREFIX + skuId);
            lock.lock();
            try {
                Integer skuTotalStock = skuStockService.getTotalBySkuId(skuId);
                SkuStock skuStock = skuStockService.getBySkuId(skuId);
                redoZone(skuId, skuTotalStock, skuStock.getSpuId());
            } finally {
                lock.unlock();
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unZoneByCollection(Collection<Long> skuIds) {
        if (CollectionUtil.isEmpty(skuIds)) {
            return;
        }
        log.info("取消sku库存分片。。。{}", skuIds);
        List<String> keys = new ArrayList<>(Constant.INITIAL_CAPACITY);
        for (Long skuId : skuIds) {
            RLock lock = redissonClient.getLock(LockCacheNames.LOCK_ZONE_PREFIX + skuId);
            lock.lock();
            try {
                Integer mainStock = skuStockMapper.getStock(skuId);
                log.info("sku-mainStock: " + mainStock);
                Integer sumLessZone = skuStockZoneMapper.getStockBySkuId(skuId);
                log.info("sku-zoneStock: " + sumLessZone);
                SkuStock skuStock = skuStockService.getBySkuId(skuId);
                undoZone(skuId, mainStock + sumLessZone, skuStock.getSpuId());
                RedisUtil.del(ProductCacheNames.SPU_ZONE + CacheNames.UNION + skuStock.getSpuId());
                RedisUtil.del(ProductCacheNames.SKU_ZONE_INFO + CacheNames.UNION + skuId);
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * 获取主分片库存
     */
    private int getMainStock(int stock, int zoneNum) {
        // 每个分片可以分配的库存数量，可能小于100（这里的平均库存并不是真正的平均库存，因为这里进行了线上取整）
        // 比如平均库存是 333.33 可以能会变成334
        int everyZoneStockRoundUp =divToRoundUp(stock, zoneNum + 1);

        if (everyZoneStockRoundUp < SkuZoneConstant.ZONE_MIN_STOCK) {
            return SkuZoneConstant.ZONE_MIN_STOCK;
        } else {
            // 平均库存向上取整，因为就算是平均库存
            return everyZoneStockRoundUp;
        }
    }

    /**
     * 获取次分片的数据
     */
    private List<SkuStockZone> getSkuStockZones(Long skuId, int stock,  int zoneNum, Long spuId, boolean isUpdate) {
        List<SkuStockZone> skuStockZones = new ArrayList<>(SkuZoneConstant.MAX_ZONE_NUM);
        // 给一个库存分成10个片
        for (int i = 0; i < SkuZoneConstant.MAX_ZONE_NUM; i++) {
            SkuStockZone skuStockZone = new SkuStockZone();
            skuStockZone.setSkuId(skuId);
            skuStockZone.setZoneNo(i);
            skuStockZone.setSpuId(spuId);
            // 前几个进行平分，后面的库存为0
            if (i< zoneNum) {
                int zoneStock = divToRoundUp(stock, zoneNum - i);
                // 能够平分就平分
                skuStockZone.setStock(zoneStock);
                // 减少剩余库存
                stock -= zoneStock;
            } else{
                skuStockZone.setStock(0);
            }
            if (!isUpdate) {
                skuStockZone.setLockStock(0);
            }
            skuStockZones.add(skuStockZone);
        }
        return skuStockZones;
    }


    /**
     * 预计要分几个片
     */
    private int getZoneNum(Integer stock){
        // SkuZoneConstant.MAX_ZONE_NUM + 1 这里面的1表示主分片
        // 什么是主分片？sku_stock 这里面的库存进行分片之后，其实会变成 1 + n的模式，一个主分片 + 10个zone
        // 也就是说总库存变为 sku_stock.stock + 10个zone.stock
        // 每个分片库存（包括主分片）至少为100个库存，如果不能达到这些要求，则不会进行库存分片
        if (stock < SkuZoneConstant.ZONE_MIN_STOCK * (SkuZoneConstant.MAX_ZONE_NUM + 1)) {
            // 向上取整 - 1（去掉主分片）
            return divToRoundUp(stock,SkuZoneConstant.ZONE_MIN_STOCK) - 1;
        }
        return SkuZoneConstant.MAX_ZONE_NUM;
    }

    /**
     * 两个数相除然后向上取整
     * @param a 除数
     * @param b 被除数
     * @return 返回值
     */
    private int divToRoundUp(int a, int b) {
        return new BigDecimal(a).divide(new BigDecimal(b),0, RoundingMode.UP).intValue();
    }

}

